// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "zencore.h"

#include <zencore/memory.h>
#include <zencore/thread.h>

#include <vector>

namespace zen {

/**
 * Binary stream writer
 */

class BinaryWriter
{
public:
	inline BinaryWriter() = default;
	~BinaryWriter()		  = default;

	inline void Write(const void* DataPtr, size_t ByteCount)
	{
		Write(DataPtr, ByteCount, m_Offset);
		m_Offset += ByteCount;
	}

	inline void Write(MemoryView Memory) { Write(Memory.GetData(), Memory.GetSize()); }
	void		Write(std::initializer_list<const MemoryView> Buffers);

	inline uint64_t CurrentOffset() const { return m_Offset; }

	inline const uint8_t* Data() const { return m_Buffer.data(); }
	inline const uint8_t* GetData() const { return m_Buffer.data(); }
	inline uint64_t		  Size() const { return m_Offset; }
	inline uint64_t		  GetSize() const { return m_Offset; }
	void				  Reset();

	inline MemoryView		 GetView() const { return MemoryView(m_Buffer.data(), m_Offset); }
	inline MutableMemoryView GetMutableView() { return MutableMemoryView(m_Buffer.data(), m_Offset); }

private:
	std::vector<uint8_t> m_Buffer;
	uint64_t			 m_Offset = 0;

	void Write(const void* DataPtr, size_t ByteCount, uint64_t Offset);
};

inline MemoryView
MakeMemoryView(const BinaryWriter& Stream)
{
	return MemoryView(Stream.Data(), Stream.Size());
}

/**
 * Binary stream reader
 */

class BinaryReader
{
public:
	inline BinaryReader(const void* Buffer, uint64_t Size) : m_BufferBase(reinterpret_cast<const uint8_t*>(Buffer)), m_BufferSize(Size) {}
	inline BinaryReader(MemoryView Buffer)
	: m_BufferBase(reinterpret_cast<const uint8_t*>(Buffer.GetData()))
	, m_BufferSize(Buffer.GetSize())
	{
	}

	inline void Read(void* DataPtr, size_t ByteCount)
	{
		ZEN_ASSERT(m_Offset + ByteCount <= m_BufferSize);
		memcpy(DataPtr, m_BufferBase + m_Offset, ByteCount);
		m_Offset += ByteCount;
	}
	inline MemoryView GetView(size_t ByteCount) const
	{
		ZEN_ASSERT(m_Offset + ByteCount <= m_BufferSize);
		return MemoryView((const void*)(m_BufferBase + m_Offset), (const void*)(m_BufferBase + m_Offset + ByteCount));
	}

	inline uint64_t Size() const { return m_BufferSize; }
	inline uint64_t GetSize() const { return Size(); }
	inline uint64_t CurrentOffset() const { return m_Offset; }
	inline uint64_t Remaining() const { return m_BufferSize - m_Offset; }
	inline void		Skip(size_t ByteCount)
	{
		ZEN_ASSERT(m_Offset + ByteCount <= m_BufferSize);
		m_Offset += ByteCount;
	};

protected:
	const uint8_t* m_BufferBase;
	uint64_t	   m_BufferSize;
	uint64_t	   m_Offset = 0;
};

/**
 * Archive base class implementation
 *
 * Roughly mimics UE's FArchive class, but without all the non-essential functionality
 */
class Archive
{
public:
	/** Attempts to set the current offset into backing data storage, this will do nothing if there is no storage. */
	virtual void	Seek(uint64_t InPos)			   = 0;
	virtual int64_t Tell()							   = 0;
	virtual void	Serialize(void* V, int64_t Length) = 0;
	inline bool		IsError() { return false; }
};

class BufferReader : public Archive, private BinaryReader
{
public:
	BufferReader(const void* Buffer, uint64_t Size) : BinaryReader(Buffer, Size) {}

	virtual void	Seek(uint64_t InPos) override;
	virtual int64_t Tell() override;
	virtual void	Serialize(void* V, int64_t Length) override;
};

void stream_forcelink();  // internal

}  // namespace zen
